package drds.binlog.event;

import drds.binlog.LogByteBlock;
import drds.binlog.LogContext;
import drds.binlog.LogEvent;

import java.util.BitSet;

/**
 * Common base class for all row-containing log events.
 */
public abstract class RowsLogEvent extends LogEvent
{

    /**
     * Last event of a statement
     */
    public static final int STMT_END_F = 1;
    /**
     * Value of the OPTION_NO_FOREIGN_KEY_CHECKS flag in thd->options
     */
    public static final int NO_FOREIGN_KEY_CHECKS_F = (1 << 1);
    /**
     * Value of the OPTION_RELAXED_UNIQUE_CHECKS flag in thd->options
     */
    public static final int RELAXED_UNIQUE_CHECKS_F = (1 << 2);
    /**
     * Indicates that rows in this event are complete, that is contain values
     * for all columns of the tableMapLogEvent.
     */
    public static final int COMPLETE_ROWS_F = (1 << 3);
    /* RW = "RoWs" */
    public static final int RW_MAPID_OFFSET = 0;
    public static final int RW_FLAGS_OFFSET = 6;
    public static final int RW_VHLEN_OFFSET = 8;
    public static final int RW_V_TAG_LEN = 1;
    public static final int RW_V_EXTRAINFO_TAG = 0;
    /**
     * Bitmap denoting columns available
     */
    protected final int columnLen;
    protected final BitSet columns;
    /**
     * Bitmap for columns available in the after image, if present. These fields
     * are only available for Update_rows events. Observe that the width of both
     * the before image COLS vector and the after image COLS vector is the same:
     * the number of columns of the tableMapLogEvent on the master.
     */
    protected final BitSet changeColumns;
    /**
     * Fixed data part:
     * <ul>
     * <li>6 bytes. The tableMapLogEvent ID.</li>
     * <li>2 bytes. Reserved for future use.</li>
     * </ul>
     * <p>
     * Variable data part:
     * <ul>
     * <li>Packed integer. The number of columns in the tableMapLogEvent.</li>
     * <li>Variable-sized. Bit-field indicating whether each column is used, one
     * bit per column. For this field, the amount of storage required for N
     * columns is INT((N+7)/8) bytes.</li>
     * <li>Variable-sized (for UPDATE_ROWS_LOG_EVENT only). Bit-field indicating
     * whether each column is used in the UPDATE_ROWS_LOG_EVENT after-image; one
     * bit per column. For this field, the amount of storage required for N
     * columns is INT((N+7)/8) bytes.</li>
     * <li>Variable-sized. A sequence of zero or more rows. The end is
     * determined by the size of the event. Each row has the following format:
     * <ul>
     * <li>Variable-sized. Bit-field indicating whether each field in the row is
     * NULL. Only columns that are "used" according to the second field in the
     * variable data part are listed here. If the second field in the variable
     * data part has N one-bits, the amount of storage required for this field
     * is INT((N+7)/8) bytes.</li>
     * <li>Variable-sized. The row-image, containing values of all tableMapLogEvent fields.
     * This only lists tableMapLogEvent fields that are used (according to the second field
     * of the variable data part) and non-NULL (according to the previous
     * field). In other words, the number of values listed here is equal to the
     * number of zero bits in the previous field (not counting padding bits in
     * the last byte). The format of each value is described in the
     * log_event_print_value() function in log_event.cc.</li>
     * <li>(for UPDATE_ROWS_EVENT only) the previous two fields are repeated,
     * representing a second tableMapLogEvent row.</li>
     * </ul>
     * </ul>
     * Source : http://forge.mysql.com/wiki/MySQL_Internals_Binary_Log
     */
    private final long tableId;                           /* Table ID */
    /**
     * XXX: Don't handle bytes in another thread.
     */
    private final LogByteBlock rowsBuf;                           /*
                                                                  * The rows in
                                                                  * packed
                                                                  * format
                                                                  */
    /**
     * enum enum_flag These definitions allow you to combine the flags into an
     * appropriate flag set using the normal bitwise operators. The implicit
     * conversion from an enum-constant to an integer is accepted by the
     * compiler, which is then used to set the real set of flags.
     */
    private final int flags;
    private TableMapLogEvent tableMapLogEvent;                             /*
                                                                  * The tableMapLogEvent
                                                                  * the rows
                                                                  * belong to
                                                                  */

    public RowsLogEvent(LogHeader logHeader, LogByteBlock logByteBlock, FormatDescriptionLogEvent formatDescriptionLogEvent)
    {
        super(logHeader);

        final int commonHeaderLength = formatDescriptionLogEvent.commonHeaderLength;
        final int postHeaderLength = formatDescriptionLogEvent.postHeaderLengths[logHeader.type - 1];
        int headerLen = 0;
        logByteBlock.index(commonHeaderLength + RW_MAPID_OFFSET);
        if (postHeaderLength == 6)
        {
            /*
             * Master is of an intermediate source tree before 5.1.4. Id is 4
             * bytes
             */
            tableId = logByteBlock.getUint32BasedOnIndex();
        } else
        {
            tableId = logByteBlock.getUlong48BasedOnIndex(); // RW_FLAGS_OFFSET
        }
        flags = logByteBlock.getUint16BasedOnIndex();

        if (postHeaderLength == FormatDescriptionLogEvent.ROWS_HEADER_LEN_V2)
        {
            headerLen = logByteBlock.getUint16BasedOnIndex();
            headerLen -= 2;
            int start = logByteBlock.index$originIndex();
            int end = start + headerLen;
            for (int i = start; i < end; )
            {
                switch (logByteBlock.getUint8(i++))
                {
                    case RW_V_EXTRAINFO_TAG:
                        // int infoLen = bytes.getUint8BasedOnIndex();
                        logByteBlock.index(i + EXTRA_ROW_INFO_LEN_OFFSET);
                        int checkLen = logByteBlock.getUint8BasedOnIndex(); // EXTRA_ROW_INFO_LEN_OFFSET
                        int val = checkLen - EXTRA_ROW_INFO_HDR_BYTES;
                        assert (logByteBlock.getUint8BasedOnIndex() == val); // EXTRA_ROW_INFO_FORMAT_OFFSET
                        for (int j = 0; j < val; j++)
                        {
                            assert (logByteBlock.getUint8BasedOnIndex() == val); // EXTRA_ROW_INFO_HDR_BYTES
                            // + i
                        }
                        break;
                    default:
                        i = end;
                        break;
                }
            }
        }

        logByteBlock.index(commonHeaderLength + postHeaderLength + headerLen);
        columnLen = (int) logByteBlock.getPackedLong();
        columns = logByteBlock.getBitmap(columnLen);

        if (logHeader.type == UPDATE_ROWS_EVENT_V1 || logHeader.type == UPDATE_ROWS_EVENT)
        {
            changeColumns = logByteBlock.getBitmap(columnLen);
        } else
        {
            changeColumns = columns;
        }

        // XXX: Don't handle bytes in another thread.
        int dataSize = logByteBlock.limit() - logByteBlock.index$originIndex();
        rowsBuf = logByteBlock.duplicateBasedOnIndex(dataSize);
    }

    public final void fillTableMapLogEvent(LogContext logContext)
    {
        tableMapLogEvent = logContext.getTableMapLogEvent(tableId);

        // end of statement check:
        if ((flags & RowsLogEvent.STMT_END_F) != 0)
        {
            // Now is safe to clear ignored map (clear_tables will also
            // delete original tableMapLogEvent map events stored in the map).
            logContext.clearTableIdToTableMapLogEventMap();
        }
    }

    public final long getTableId()
    {
        return tableId;
    }

    public final TableMapLogEvent getTableMapLogEvent()
    {
        return tableMapLogEvent;
    }

    public final BitSet getColumns()
    {
        return columns;
    }

    public final BitSet getChangeColumns()
    {
        return changeColumns;
    }

    public final RowsLogBuffer getRowsBuf(String charsetName)
    {
        return new RowsLogBuffer(rowsBuf, columnLen, charsetName);
    }

    public final int getFlags(final int flags)
    {
        return this.flags & flags;
    }
}
